package com.macro.mall.portal.controller;

import com.github.binarywang.wxpay.bean.coupon.*;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxScanPayNotifyResult;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.request.*;
import com.github.binarywang.wxpay.bean.result.*;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.util.SignUtils;
import com.google.zxing.common.BitMatrix;
import com.macro.mall.common.api.CommonResult;
import com.macro.mall.common.utils.DateUtil;
import com.macro.mall.common.utils.QrCodeUtil;
import com.macro.mall.mapper.OmsOrderItemMapper;
import com.macro.mall.mapper.OmsOrderMapper;
import com.macro.mall.model.OmsOrder;
import com.macro.mall.model.OmsOrderItem;
import com.macro.mall.model.OmsOrderItemExample;
import com.macro.mall.portal.domain.JhhWxPayNotifyResponse;
import com.macro.mall.portal.domain.TestPayResponse;
import com.macro.mall.portal.util.SignUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import sun.misc.BASE64Encoder;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

@Log4j2
@RestController
@Api(tags = "WXPayController", description = "微信支付")
@RequestMapping("/wx/pay")
public class WxPayController {
    private WxPayService wxService;

    @Autowired
    public WxPayController(WxPayService wxService) {
        this.wxService = wxService;
    }

    /**
     * 统一下单
     * 在发起微信支付前，需要调用统一下单接口，返回微信支付的所需要的参数
     * 接口地址：https://api.mch.weixin.qq.com/pay/unifiedorder
     *
     * @param request 请求对象，注意一些参数如appid、mchid等不用设置，方法内会自动从配置对象中获取到（前提是对应配置中已经设置）
     */
    @PostMapping("/getPayInfo")
    public WxPayMpOrderResult getPayInfo(@RequestBody WxPayUnifiedOrderRequest request, HttpServletRequest httpServletRequest) throws WxPayException, Exception {
        request.setOutTradeNo(SignUtil.buildRandom(10));//随机字订单号
        request.setSpbillCreateIp(SignUtil.getRemortIP(httpServletRequest));//用户ip
        request.setTradeType("JSAPI");//SAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付，统一下单接口trade_type的传参可参考这里
        request.setNonceStr(SignUtil.createNonceStr());//随机字符串
        request.setNotifyUrl("http://qwrerewrwqewq/wechat/pay/PayResultNotify");//回调通知支付结果地址(必须外网能访问的地址)
        request.setDeviceInfo("WEB");  //客户终端类型
        request.setSignType("MD5");//加密方式（必须参数，虽然官方文档说是非必需，亲测不加一直报签名错误）
        return this.wxService.createOrder(request);
    }

    /**
     * 接受微信支付返回通知
     *
     * @param request
     * @param response
     * @throws IOException
     */
    @RequestMapping("/PayResultNotify")
    public void PayResultNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
        log.info("微信支付返回通知函数开始---------------------");

        InputStream inStream = request.getInputStream();
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        outSteam.close();
        inStream.close();
        String result = new String(outSteam.toByteArray(), "utf-8");
        boolean isPayOk = false;
        WxPayOrderNotifyResult wxPayOrderNotifyResult = null;

        // 此处调用订单查询接口验证是否交易成功
        try {
            wxPayOrderNotifyResult = wxService.parseOrderNotifyResult(result);
            if ("SUCCESS".equals(wxPayOrderNotifyResult.getResultCode())) {
                isPayOk = true;
            }
            log.info("解析数据:" + wxPayOrderNotifyResult.toString());
        } catch (WxPayException e) {
            e.printStackTrace();
        }

        String noticeStr = "";


        // 支付成功，商户处理后同步返回给微信参数
        PrintWriter writer = response.getWriter();
        if (isPayOk) {
            //建议在这里处理付款完成的业务（虽然前端也可以处理后续业务，但是前端处理并不安全，例如：客户突然关闭浏览器了等情况，付款成功后续的业务将中断）
            System.out.println("===============付款成功，业务处理完毕==============");
            // 通知微信已经收到消息，不要再给我发消息了，否则微信会8连击调用本接口
            noticeStr = setXML("SUCCESS", "OK");
            log.info("收到通知返回给微信api信息:-----------" + noticeStr);
            writer.write(noticeStr);
            writer.flush();
        } else {
            // 支付失败， 记录流水失败
            noticeStr = setXML("FAIL", "");
            writer.write(noticeStr);
            writer.flush();
            System.out.println("===============支付失败==============");
        }
    }

    public static String setXML(String return_code, String return_msg) {
        return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>";
    }

    /**
     * <pre>
     * 查询订单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2)
     * 该接口提供所有微信支付订单的查询，商户可以通过查询订单接口主动查询订单状态，完成下一步的业务逻辑。
     * 需要调用查询接口的情况：
     * ◆ 当商户后台、网络、服务器等出现异常，商户系统最终未接收到支付通知；
     * ◆ 调用支付接口后，返回系统错误或未知交易状态情况；
     * ◆ 调用被扫支付API，返回USERPAYING的状态；
     * ◆ 调用关单或撤销接口API之前，需确认支付状态；
     * 接口地址：https://api.mch.weixin.qq.com/pay/orderquery
     * </pre>
     *
     * @param transactionId 微信订单号
     * @param outTradeNo    商户系统内部的订单号，当没提供transactionId时需要传这个。
     */
    @RequestMapping(value = "/queryOrder", method = RequestMethod.POST)
    @ApiOperation(value = "查询订单")
    public WxPayOrderQueryResult queryOrder(@RequestParam(required = false) String transactionId,
                                            @RequestParam(required = false) String outTradeNo)
            throws WxPayException {
        return this.wxService.queryOrder(transactionId, outTradeNo);
    }

    @ApiOperation(value = "查询订单")
    public WxPayOrderQueryResult queryOrder(@RequestBody WxPayOrderQueryRequest wxPayOrderQueryRequest) throws WxPayException {
        return this.wxService.queryOrder(wxPayOrderQueryRequest);
    }

    /**
     * <pre>
     * 关闭订单
     * 应用场景
     * 以下情况需要调用关单接口：
     * 1. 商户订单支付失败需要生成新单号重新发起支付，要对原订单号调用关单，避免重复支付；
     * 2. 系统下单后，用户支付超时，系统退出不再受理，避免用户继续，请调用关单接口。
     * 注意：订单生成后不能马上调用关单接口，最短调用时间间隔为5分钟。
     * 接口地址：https://api.mch.weixin.qq.com/pay/closeorder
     * 是否需要证书：   不需要。
     * </pre>
     *
     * @param outTradeNo 商户系统内部的订单号
     */
    @ApiOperation(value = "关闭订单")
    @GetMapping("/closeOrder/{outTradeNo}")
    public WxPayOrderCloseResult closeOrder(@PathVariable String outTradeNo) throws WxPayException {
        return this.wxService.closeOrder(outTradeNo);
    }

    @ApiOperation(value = "关闭订单")
    @PostMapping("/closeOrder")
    public WxPayOrderCloseResult closeOrder(@RequestBody WxPayOrderCloseRequest wxPayOrderCloseRequest) throws WxPayException {
        return this.wxService.closeOrder(wxPayOrderCloseRequest);
    }

    /**
     * 调用统一下单接口，并组装生成支付所需参数对象.
     *
     * @param request 统一下单请求参数
     * @param <T>     请使用{@link com.github.binarywang.wxpay.bean.order}包下的类
     * @return 返回 {@link com.github.binarywang.wxpay.bean.order}包下的类对象
     */
    @ApiOperation(value = "统一下单，并组装所需支付参数")
    @PostMapping("/createOrder")
    public <T> T createOrder(@RequestBody WxPayUnifiedOrderRequest request) throws WxPayException {
        return this.wxService.createOrder(request);
    }

    /**
     * 统一下单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1)
     * 在发起微信支付前，需要调用统一下单接口，获取"预支付交易会话标识"
     * 接口地址：https://api.mch.weixin.qq.com/pay/unifiedorder
     *
     * @param request 请求对象，注意一些参数如appid、mchid等不用设置，方法内会自动从配置对象中获取到（前提是对应配置中已经设置）
     */
    @ApiOperation(value = "原生的统一下单接口")
    @PostMapping("/unifiedOrder")
    public WxPayUnifiedOrderResult unifiedOrder(@RequestBody WxPayUnifiedOrderRequest request) throws WxPayException {
        log.info("WxPayUnifiedOrderRequest:{}", request);
        return this.wxService.unifiedOrder(request);
    }

    /**
     * <pre>
     * 微信支付-申请退款
     * 详见 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
     * 接口链接：https://api.mch.weixin.qq.com/secapi/pay/refund
     * </pre>
     *
     * @param request 请求对象
     * @return 退款操作结果
     */
    @ApiOperation(value = "退款")
    @PostMapping("/refund")
    public WxPayRefundResult refund(@RequestBody WxPayRefundRequest request) throws WxPayException {
        return this.wxService.refund(request);
    }

    /**
     * <pre>
     * 微信支付-查询退款
     * 应用场景：
     *  提交退款申请后，通过调用该接口查询退款状态。退款有一定延时，用零钱支付的退款20分钟内到账，
     *  银行卡支付的退款3个工作日后重新查询退款状态。
     * 详见 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5
     * 接口链接：https://api.mch.weixin.qq.com/pay/refundquery
     * </pre>
     * 以下四个参数四选一
     *
     * @param transactionId 微信订单号
     * @param outTradeNo    商户订单号
     * @param outRefundNo   商户退款单号
     * @param refundId      微信退款单号
     * @return 退款信息
     */
    @ApiOperation(value = "退款查询")
    @GetMapping("/refundQuery")
    public WxPayRefundQueryResult refundQuery(@RequestParam(required = false) String transactionId,
                                              @RequestParam(required = false) String outTradeNo,
                                              @RequestParam(required = false) String outRefundNo,
                                              @RequestParam(required = false) String refundId)
            throws WxPayException {
        return this.wxService.refundQuery(transactionId, outTradeNo, outRefundNo, refundId);
    }

    @ApiOperation(value = "退款查询")
    @PostMapping("/refundQuery")
    public WxPayRefundQueryResult refundQuery(@RequestBody WxPayRefundQueryRequest wxPayRefundQueryRequest) throws WxPayException {
        return this.wxService.refundQuery(wxPayRefundQueryRequest);
    }

    @ApiOperation(value = "支付回调通知处理")
    @PostMapping("/notify/order")
    public String parseOrderNotifyResult(@RequestBody String xmlData) throws WxPayException {
        final WxPayOrderNotifyResult notifyResult = this.wxService.parseOrderNotifyResult(xmlData);
        // TODO 根据自己业务场景需要构造返回对象
        return WxPayNotifyResponse.success("成功");
    }

    @ApiOperation(value = "退款回调通知处理")
    @PostMapping("/notify/refund")
    public String parseRefundNotifyResult(@RequestBody String xmlData) throws WxPayException {
        final WxPayRefundNotifyResult result = this.wxService.parseRefundNotifyResult(xmlData);
        // TODO 根据自己业务场景需要构造返回对象
        return WxPayNotifyResponse.success("成功");
    }

    @Autowired
    private OmsOrderMapper orderMapper;
    @Autowired
    private OmsOrderItemMapper orderItemMapper;
    @Value("${wx.notifyUrl}")
    private String NOTIFYURL;
    @Value("${wx.pay.mchKey}")
    private String MCHKEY;

    @ApiOperation(value = "扫码支付回调通知处理")
    @PostMapping("/notify/scanpay")
    public void parseScanPayNotifyResult(String xmlData, HttpServletRequest request, HttpServletResponse response) throws IOException, WxPayException {
        JhhWxPayNotifyResponse payResponse = new JhhWxPayNotifyResponse();
        log.info("扫码支付回调通知处理");
        log.info(xmlData);
        String toXml = null;
        try {
            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(request.getInputStream(), "UTF-8"));
            String line;
            String resu = "";
            while ((line = reader.readLine()) != null) {
                resu += line;
            }
            log.info("业务验证提示信息! URL={} 参数：{}", request.getRequestURL().toString(), resu);
            final WxScanPayNotifyResult result = this.wxService.parseScanPayNotifyResult(resu);
            log.info("result.getMchId()：{}", result.getMchId());
            log.info("result.getAppid()：{}", result.getAppid());
            log.info("result.getOpenid()：{}", result.getOpenid());
            log.info("result.getProductId()：{}", result.getProductId());


            OmsOrder omsOrder = orderMapper.selectByPrimaryKey(Long.parseLong(result.getProductId()));
            OmsOrderItemExample omsOrderItemExample = new OmsOrderItemExample();
            OmsOrderItemExample.Criteria criteria = omsOrderItemExample.createCriteria();
            criteria.andOrderIdEqualTo(Long.parseLong(result.getProductId()));
            List<OmsOrderItem> omsOrderItems = orderItemMapper.selectByExample(omsOrderItemExample);
            String productName = omsOrderItems.get(0).getProductName();

            log.info("result.getIsSubscribe()：{}", result.getIsSubscribe());
            WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest = new WxPayUnifiedOrderRequest();
            wxPayUnifiedOrderRequest.setMchId(result.getMchId());
            wxPayUnifiedOrderRequest.setAppid(result.getAppid());
            wxPayUnifiedOrderRequest.setOpenid(result.getOpenid());
            wxPayUnifiedOrderRequest.setProductId(result.getProductId());
            wxPayUnifiedOrderRequest.setBody("好货集-" + productName);
            wxPayUnifiedOrderRequest.setAttach(omsOrder.getOrderSn());
            BigDecimal multiply = omsOrder.getTotalAmount().multiply(new BigDecimal(100));
            wxPayUnifiedOrderRequest.setTotalFee(multiply.intValue());
            wxPayUnifiedOrderRequest.setTimeStart(DateUtil.dateTimeNow());
            wxPayUnifiedOrderRequest.setTradeType("NATIVE");
            wxPayUnifiedOrderRequest.setOutTradeNo(result.getProductId());
            wxPayUnifiedOrderRequest.setNotifyUrl(NOTIFYURL);
            //用 getLocalHost() 方法创建的InetAddress的对象
            InetAddress address = InetAddress.getLocalHost();
            wxPayUnifiedOrderRequest.setSpbillCreateIp(address.getHostAddress());


            WxScanPayNotifyResult wxScanPayNotifyResult = BaseWxPayResult.fromXML(resu, WxScanPayNotifyResult.class);

            WxPayUnifiedOrderResult wxPayUnifiedOrderResult = this.wxService.unifiedOrder(wxPayUnifiedOrderRequest);
            log.info("wxPayUnifiedOrderResult.getErrCode()::::{}", wxPayUnifiedOrderResult.toString());
            log.info("wxPayUnifiedOrderResult.getErrCode()::::{}", wxPayUnifiedOrderResult.getReturnCode());

            if (!wxPayUnifiedOrderResult.getReturnCode().endsWith("SUCCESS")) {
                throw new Exception(wxPayUnifiedOrderResult.getReturnMsg());
            } else {
                if (!wxPayUnifiedOrderResult.getResultCode().endsWith("SUCCESS")) {
                    throw new Exception(wxPayUnifiedOrderResult.getErrCodeDes());
                }
            }

            payResponse.setReturnCode("SUCCESS");
            payResponse.setAppid(result.getAppid());
            payResponse.setMchId(result.getMchId());
            payResponse.setNonceStr(wxScanPayNotifyResult.getNonceStr());
            payResponse.setPrepayId(wxPayUnifiedOrderResult.getPrepayId());
            payResponse.setResultCode("SUCCESS");

            String md5 = SignUtils.createSign(payResponse, "MD5", MCHKEY, null);
            payResponse.setSign(md5);
            log.info("返回输出为：{}", JhhWxPayNotifyResponse.toXml(payResponse));
            toXml = JhhWxPayNotifyResponse.toXml(payResponse);
        } catch (Exception e) {
            payResponse.setResultCode("FAIL");
            payResponse.setErrCodeDes(e.getMessage());
        } finally {
            //通知微信 预下单成功
            BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
            out.write(toXml.getBytes());
            out.flush();
            out.close();
        }
    }

    /**
     * 发送微信红包给个人用户
     * <pre>
     * 文档详见:
     * 发送普通红包 https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3
     *  接口地址：https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack
     * 发送裂变红包 https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_5&index=4
     *  接口地址：https://api.mch.weixin.qq.com/mmpaymkttransfers/sendgroupredpack
     * </pre>
     *
     * @param request 请求对象
     */
    @ApiOperation(value = "发送红包")
    @PostMapping("/sendRedpack")
    public WxPaySendRedpackResult sendRedpack(@RequestBody WxPaySendRedpackRequest request) throws WxPayException {
        return this.wxService.sendRedpack(request);
    }

    /**
     * <pre>
     *   查询红包记录
     *   用于商户对已发放的红包进行查询红包的具体信息，可支持普通红包和裂变包。
     *   请求Url	https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo
     *   是否需要证书	是（证书及使用说明详见商户证书）
     *   请求方式	POST
     * </pre>
     *
     * @param mchBillNo 商户发放红包的商户订单号，比如10000098201411111234567890
     */
    @ApiOperation(value = "查询红包")
    @GetMapping("/queryRedpack/{mchBillNo}")
    public WxPayRedpackQueryResult queryRedpack(@PathVariable String mchBillNo) throws WxPayException {
        return this.wxService.queryRedpack(mchBillNo);
    }

    /**
     * <pre>
     * 扫码支付模式一生成二维码的方法
     * 二维码中的内容为链接，形式为：
     * weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX
     * 其中XXXXX为商户需要填写的内容，商户将该链接生成二维码，如需要打印发布二维码，需要采用此格式。商户可调用第三方库生成二维码图片。
     * 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
     * </pre>
     *
     * @param productId  产品Id
     * @param logoFile   商户logo图片的文件对象，可以为空
     * @param sideLength 要生成的二维码的边长，如果为空，则取默认值400
     * @return 生成的二维码的字节数组
     */
    public byte[] createScanPayQrcodeMode1(String productId, File logoFile, Integer sideLength) {
        return this.wxService.createScanPayQrcodeMode1(productId, logoFile, sideLength);
    }

    private static final int BLACK = 0xFF000000;
    private static final int WHITE = 0xFFFFFFFF;

    /**
     * <pre>
     * 扫码支付模式一生成二维码的方法
     * 二维码中的内容为链接，形式为：
     * weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX
     * 其中XXXXX为商户需要填写的内容，商户将该链接生成二维码，如需要打印发布二维码，需要采用此格式。商户可调用第三方库生成二维码图片。
     * 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
     * </pre>
     *
     * @param productId 产品Id
     * @return 生成的二维码URL连接
     */
    @ResponseBody
    @ApiOperation(value = "生成付款二维码")
    @GetMapping("/createQRImage/{productId}")
    public CommonResult<String> createScanPayQrcodeMode1(@PathVariable String productId, HttpServletResponse response) {
        String codeUrl = this.wxService.createScanPayQrcodeMode1(productId);
        log.info("生成的地址为：{}", codeUrl);
        BitMatrix qrCode = QrCodeUtil.createQrCode(codeUrl);
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        String png_base64 = null;
        try {
            BufferedImage bufferedImage = toBufferedImage(qrCode);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();//io流
            ImageIO.write(bufferedImage, "png", baos);//写入流中
            byte[] bytes = baos.toByteArray();//转换成字节
            BASE64Encoder encoder = new BASE64Encoder();
            png_base64 = encoder.encodeBuffer(bytes).trim();//转换成base64串
            png_base64 = png_base64.replaceAll("\n", "").replaceAll("\r", "");//删除 \r\n
        } catch (IOException e) {
            e.printStackTrace();
        }
        log.info("png_base64::::{}", png_base64);
        return CommonResult.success(png_base64);
    }

    private static BufferedImage toBufferedImage(BitMatrix matrix) {
        int width = matrix.getWidth();
        int height = matrix.getHeight();
        BufferedImage image = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
            }
        }
        return image;
    }

    /**
     * <pre>
     * 扫码支付模式二生成二维码的方法
     * 对应链接格式：weixin：//wxpay/bizpayurl?sr=XXXXX。请商户调用第三方库将code_url生成二维码图片。
     * 该模式链接较短，生成的二维码打印到结账小票上的识别率较高。
     * 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
     * </pre>
     *
     * @param codeUrl    微信返回的交易会话的二维码链接
     * @param logoFile   商户logo图片的文件对象，可以为空
     * @param sideLength 要生成的二维码的边长，如果为空，则取默认值400
     * @return 生成的二维码的字节数组
     */
    public byte[] createScanPayQrcodeMode2(String codeUrl, File logoFile, Integer sideLength) {
        return this.wxService.createScanPayQrcodeMode2(codeUrl, logoFile, sideLength);
    }

    /**
     * <pre>
     * 交易保障
     * 应用场景：
     *  商户在调用微信支付提供的相关接口时，会得到微信支付返回的相关信息以及获得整个接口的响应时间。
     *  为提高整体的服务水平，协助商户一起提高服务质量，微信支付提供了相关接口调用耗时和返回信息的主动上报接口，
     *  微信支付可以根据商户侧上报的数据进一步优化网络部署，完善服务监控，和商户更好的协作为用户提供更好的业务体验。
     * 接口地址： https://api.mch.weixin.qq.com/payitil/report
     * 是否需要证书：不需要
     * </pre>
     */
    @ApiOperation(value = "提交交易保障数据")
    @PostMapping("/report")
    public void report(@RequestBody WxPayReportRequest request) throws WxPayException {
        this.wxService.report(request);
    }

    /**
     * <pre>
     * 下载对账单
     * 商户可以通过该接口下载历史交易清单。比如掉单、系统错误等导致商户侧和微信侧数据不一致，通过对账单核对后可校正支付状态。
     * 注意：
     * 1、微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账单中，跟原支付单订单号一致，bill_type为REVOKED；
     * 2、微信在次日9点启动生成前一天的对账单，建议商户10点后再获取；
     * 3、对账单中涉及金额的字段单位为“元”。
     * 4、对账单接口只能下载三个月以内的账单。
     * 接口链接：https://api.mch.weixin.qq.com/pay/downloadbill
     * 详情请见: <a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6">下载对账单</a>
     * </pre>
     *
     * @param billDate   对账单日期 bill_date	下载对账单的日期，格式：20140603
     * @param billType   账单类型	bill_type	ALL，返回当日所有订单信息，默认值，SUCCESS，返回当日成功支付的订单，REFUND，返回当日退款订单
     * @param tarType    压缩账单	tar_type	非必传参数，固定值：GZIP，返回格式为.gzip的压缩包账单。不传则默认为数据流形式。
     * @param deviceInfo 设备号	device_info	非必传参数，终端设备号
     * @return 保存到本地的临时文件
     */
    @ApiOperation(value = "下载对账单")
    @GetMapping("/downloadBill/{billDate}/{billType}/{tarType}/{deviceInfo}")
    public WxPayBillResult downloadBill(@PathVariable String billDate, @PathVariable String billType,
                                        @PathVariable String tarType, @PathVariable String deviceInfo) throws WxPayException {
        return this.wxService.downloadBill(billDate, billType, tarType, deviceInfo);
    }

    @ApiOperation(value = "下载对账单")
    @PostMapping("/downloadBill")
    public WxPayBillResult downloadBill(WxPayDownloadBillRequest wxPayDownloadBillRequest) throws WxPayException {
        return this.wxService.downloadBill(wxPayDownloadBillRequest);
    }

    /**
     * <pre>
     * 提交刷卡支付
     * 文档地址：https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1
     * 应用场景：
     * 收银员使用扫码设备读取微信用户刷卡授权码以后，二维码或条码信息传送至商户收银台，由商户收银台或者商户后台调用该接口发起支付。
     * 提醒1：提交支付请求后微信会同步返回支付结果。当返回结果为“系统错误”时，商户系统等待5秒后调用【查询订单API】，查询支付实际交易结果；当返回结果为“USERPAYING”时，商户系统可设置间隔时间(建议10秒)重新查询支付结果，直到支付成功或超时(建议30秒)；
     * 提醒2：在调用查询接口返回后，如果交易状况不明晰，请调用【撤销订单API】，此时如果交易失败则关闭订单，该单不能再支付成功；如果交易成功，则将扣款退回到用户账户。当撤销无返回或错误时，请再次调用。注意：请勿扣款后立即调用【撤销订单API】,建议至少15秒后再调用。撤销订单API需要双向证书。
     * 接口地址：   https://api.mch.weixin.qq.com/pay/micropay
     * 是否需要证书：不需要。
     * </pre>
     */
    @ApiOperation(value = "提交刷卡支付")
    @PostMapping("/micropay")
    public WxPayMicropayResult micropay(@RequestBody WxPayMicropayRequest request) throws WxPayException {
        return this.wxService.micropay(request);
    }

    /**
     * <pre>
     * 撤销订单API
     * 文档地址：https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_11&index=3
     * 应用场景：
     *  支付交易返回失败或支付系统超时，调用该接口撤销交易。如果此订单用户支付失败，微信支付系统会将此订单关闭；如果用户支付成功，微信支付系统会将此订单资金退还给用户。
     *  注意：7天以内的交易单可调用撤销，其他正常支付的单如需实现相同功能请调用申请退款API。提交支付交易后调用【查询订单API】，没有明确的支付结果再调用【撤销订单API】。
     *  调用支付接口后请勿立即调用撤销订单API，建议支付后至少15s后再调用撤销订单接口。
     *  接口链接 ：https://api.mch.weixin.qq.com/secapi/pay/reverse
     *  是否需要证书：请求需要双向证书。
     * </pre>
     */
    @ApiOperation(value = "撤销订单")
    @PostMapping("/reverseOrder")
    public WxPayOrderReverseResult reverseOrder(@RequestBody WxPayOrderReverseRequest request) throws WxPayException {
        return this.wxService.reverseOrder(request);
    }

    @ApiOperation(value = "获取沙箱环境签名key")
    @GetMapping("/getSandboxSignKey")
    public String getSandboxSignKey() throws WxPayException {
        return this.wxService.getSandboxSignKey();
    }

    @ApiOperation(value = "发放代金券")
    @PostMapping("/sendCoupon")
    public WxPayCouponSendResult sendCoupon(@RequestBody WxPayCouponSendRequest request) throws WxPayException {
        return this.wxService.sendCoupon(request);
    }

    @ApiOperation(value = "查询代金券批次")
    @PostMapping("/queryCouponStock")
    public WxPayCouponStockQueryResult queryCouponStock(@RequestBody WxPayCouponStockQueryRequest request) throws WxPayException {
        return this.wxService.queryCouponStock(request);
    }

    @ApiOperation(value = "查询代金券信息")
    @PostMapping("/queryCouponInfo")
    public WxPayCouponInfoQueryResult queryCouponInfo(@RequestBody WxPayCouponInfoQueryRequest request) throws WxPayException {
        return this.wxService.queryCouponInfo(request);
    }

    @ApiOperation(value = "拉取订单评价数据")
    @PostMapping("/queryComment")
    public String queryComment(Date beginDate, Date endDate, Integer offset, Integer limit) throws WxPayException {
        return this.wxService.queryComment(beginDate, endDate, offset, limit);
    }

}
